package implementations.sm_kernel;

import implementations.util.CoresAutodetect;
import interfaces.kernel.JCL_facade;
import interfaces.kernel.JCL_orb;
import interfaces.kernel.JCL_result;
import interfaces.kernel.JCL_task;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import commom.GenericConsumer;
import commom.GenericResource;
import commom.JCL_resultImpl;
import commom.JCL_taskImpl;

public class JCL_FacadeImpl implements JCL_facade {
	
	protected GenericConsumer<JCL_task>[] workers;
	protected GenericResource<JCL_task>[] r;
	protected Map<String, JCL_result> results;	
		
	protected JCL_orb<JCL_result> orb;
	
	protected static long numOfTasks;
	
	protected int numofCores;
	protected int currentWorker;
	
	private String macAddress;
	
	@SuppressWarnings("unchecked")
	protected JCL_FacadeImpl(){
		try{
			numofCores = coresAutoDetect();
			
			orb = JCL_orbImpl.getInstance();
			
			System.err.println("machine with " + numofCores + " cores");
			
			workers = new GenericConsumer[numofCores];
			
			r = new GenericResource[numofCores];
			
			results = new ConcurrentHashMap<String, JCL_result>();
			
			for(int i=0; i<numofCores; i++){
				r[i] = new GenericResource<JCL_task>();
				GenericConsumer<JCL_task> gc = new TaskConsumer<JCL_task>(r[i], results);
				gc.start();
				workers[i] = gc;
			}
			
			numOfTasks = 0;
			currentWorker = 0;
			
			macAddress = this.getMac();
						
			System.err.println("JCL started - ID: " + macAddress);
			
		}catch ( Exception e ){
			System.err.println("JCL facade constructor error");
			
		}
		
	}	
	
	private String getMac(){
		try {
			 
			InetAddress ip = InetAddress.getLocalHost();
			System.out.println("Current IP address : " + ip.getHostAddress());
	 
			NetworkInterface network = NetworkInterface.getByInetAddress(ip);
	 
			byte[] mac = network.getHardwareAddress();
	 
			System.out.print("Current MAC address : ");
	 
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < mac.length; i++) {
				sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));		
			}
			
			if (sb.length()==0) {
				System.err.println("no MAC ADDRESS");
				return ip.getHostAddress();
			}
			else {
				System.err.println(sb.toString());
				return sb.toString();			
			}
	 
		} catch (Exception e) {
	 
			try {
				InetAddress ip = InetAddress.getLocalHost();
			
				String sb = ip.getHostAddress();
				return sb;
				
			} catch (UnknownHostException e1) {
				System.err.println("cannot collect host address");
				return null;
			}
		}
	}
	

	protected int coresAutoDetect() {
		return CoresAutodetect.detect();
	}

	
	
	@Override
	public String execute(String className, String methodName, Object... args) {
		// TODO Auto-generated method stub
			
		updateTask();
		String ticket = macAddress+numOfTasks;
		
		
		try{
			
			currentWorker++;
			if(currentWorker>=numofCores) currentWorker=0;
			
			JCL_task t = new JCL_taskImpl(ticket, className, methodName, args);
			t.setTaskTime(System.nanoTime());
						
			JCL_result jclr = new JCL_resultImpl();			
			results.put(ticket, jclr);
			
			r[currentWorker].putRegister(t);
			
			return ticket;
			
		}catch (Exception e){
			System.err.println("JCL facade problem in execute(String className, String methodName, Object... args)");
			
			return String.valueOf(-1);
		}		
	}
	
	@Override
	public String execute (String objectNickname, Object... args){
				
		updateTask();
		String ticket = macAddress+numOfTasks;
		
		try{
			JCL_task t = new JCL_taskImpl(ticket, objectNickname, null, args);
			t.setTaskTime(System.nanoTime());
			
			JCL_result jclr = new JCL_resultImpl();			
			results.put(ticket, jclr);
			
			currentWorker++;
			if(currentWorker>=numofCores) currentWorker=0;
			r[currentWorker].putRegister(t);
			
			return ticket;
		}catch (Exception e){
			System.err.println("JCL facade problem in execute (String objectNickname, Object... args)");
			return String.valueOf(-1);
		}		
	}	

	private void join(String ID) {
		try{
			// TODO Auto-generated method stub
			
			JCL_result jclr = results.get(ID);
			
			if((jclr.getCorrectResult()==null)&&(jclr.getErrorResult()==null)){
				synchronized (jclr){
					jclr.wait();
				}
				
				join(ID);
			}
		}catch (Exception e){
			System.err.println("problem in JCL facade join ");
			
		}
		
	}
	
	
	@Override
	public JCL_result getResultBlocking(String ID) {
		// TODO Auto-generated method stub
		updateTask();
		
		try{
			join(ID);
			
			return results.get(ID);
			
		}catch (Exception e){
			System.err.println("problem in JCL facade getResultBlocking(String ID)");
			JCL_result jclr = new JCL_resultImpl();
			jclr.setErrorResult(e);
			
			return jclr;
		}
	}

	@Override
	public JCL_result getResultUnblocking(String ID) {
		updateTask();
		
		try{
			return results.get(ID);
		}catch (Exception e){
			System.err.println("problem in JCL facade getResultUnblocking(String ID)");
			
			JCL_result jclr = new JCL_resultImpl();
			jclr.setErrorResult(e);
			
			return jclr;
		}
	}
	
	@Override
	public JCL_result removeResult(String ID){
		try{
			updateTask();
			return results.remove(ID);
		}catch(Exception e){
			System.err.println("problem in JCL facade removeResult(String ID)");
			
			JCL_result jclr = new JCL_resultImpl();
			jclr.setErrorResult(e);
			
			return jclr;
		}
	}
	
	@Override
	public boolean destroyGlobalVar(String varName) {
		try{
			updateTask();		
		
			return orb.destroyGlobalVar(varName);
		}catch (Exception e){
			System.err.println("problem in JCL facade destroyGlobalVar(String varName)");
			return false;
		}
	}
	
	
	@Override
	public boolean setValue(String varName, Object value) {
		// TODO Auto-generated method stub
		try{
			updateTask();		
		
			return orb.setValue(varName, value);
		}catch (Exception e){
			System.err.println("problem in JCL facade setValue(String varName, Object value)");
			return false;
		}
		
			
	}

	@Override
	public JCL_result getValue(String varName) {
		try{
			updateTask();
				
			return orb.getValue(varName);
		}catch(Exception e){
			System.err.println("problem in JCL facade getValue(String varName)");
			
			JCL_result jclr = new JCL_resultImpl();
			jclr.setErrorResult(e);
			return jclr;
		}
		
	}
	
	@Override
	public boolean register(File[] f, String classToBeExecuted) {
		try{
			updateTask();
			
			return orb.register(f, classToBeExecuted);
			
		}catch(Exception e){
			
			System.err.println("problem in JCL facade register(File f, String classToBeExecuted)");
			e.printStackTrace();
			return false;
		}
	}
	
	private synchronized Class<?> registerJar(File[] fs, String classToBeExecuted) throws Exception{
		
		new File("../user_jars/").mkdir();
		
		for(File f: fs)		
			copyJarFile(new JarFile(f), new File("../user_jars/"));
					
		URL[] urls = new URL[fs.length];
		
		for(int i=0; i<fs.length; i++)						
				urls[i] = new File("../user_jars/" + fs[i].getName()).toURL(); 	
		
		JarFile jar = new JarFile("../user_jars/" + fs[0].getName());
		
	    for (Enumeration<JarEntry> entries = jar.entries() ; entries.hasMoreElements() ;)
	    {
	        JarEntry entry = entries.nextElement();
	        String file = entry.getName();
	        
	        if (file.endsWith(classToBeExecuted+ ".class"))
	        {
	           
	        	String classname = file.replace('/', '.').substring(0, file.length() - 6);
	        	
			    URLClassLoader urlCL = new URLClassLoader(urls);
		
			    Class<?> c = urlCL.loadClass(classname);
			    
			    return c;
	        }
	    }	
	    
	    
	    return null;	
		
	}
	
	private static void copyJarFile(JarFile jarFile, File destDir) throws Exception {
	       String fileName = jarFile.getName();
	       
	       String fileNameLastPart = "";
	       
	       if(fileName.lastIndexOf(File.separator)==-1)
	    	   fileNameLastPart = fileName;
	       else fileNameLastPart = fileName.substring(fileName.lastIndexOf(File.separator));
	       
	       File destFile = new File(destDir, fileNameLastPart);
	 
	       JarOutputStream jos = new JarOutputStream(new FileOutputStream(destFile));
	       Enumeration<JarEntry> entries = jarFile.entries();
	 
	       while (entries.hasMoreElements()) {
	           JarEntry entry = entries.nextElement();
	           InputStream is = jarFile.getInputStream(entry);
	 
	           //jos.putNextEntry(entry);
	           //create a new entry to avoid ZipException: invalid entry compressed size
	           jos.putNextEntry(new JarEntry(entry.getName()));
	           byte[] buffer = new byte[4096];
	           int bytesRead = 0;
	           while ((bytesRead = is.read(buffer)) != -1) {
	               jos.write(buffer, 0, bytesRead);
	           }
	           is.close();
	           jos.flush();
	           jos.closeEntry();
	       }
	       jos.close();
	}
	

	@Override
	public boolean register(Class<?> object, String nickName) {
				
		try{
			updateTask();
			return orb.register(object, nickName);
			
		}catch(Exception e){
			System.err.println("problem in JCL facade register(Class<?> object, String nickName)");
			
			return false;
		}
	}
	
	@Override
	public boolean setValueUnlocking(String varName, Object value) {
		// TODO Auto-generated method stub
		
		try{
			updateTask();
			return orb.setValueUnlocking(varName, value);
		}catch (Exception e){
			System.err.println("problem in JCL facade setValueUnlocking(String varName, Object value)");
			
			return false;
		}
	}


	@Override
	public JCL_result getValueLocking(String varName) {
		// TODO Auto-generated method stub
		
		try{
			updateTask();			
			return orb.getValueLocking(varName);	
			
		}catch (Exception e){
			System.err.println("problem in JCL facade getValueLocking(String varName)");
			
			JCL_result jclr = new JCL_resultImpl();
			jclr.setErrorResult(e);
			return jclr;
		}
	}

	
	@Override
	public void destroy(){
		try{
			
			for(int i=0; i<numofCores; i++)
				r[i].setFinished();
			
			for(GenericConsumer<JCL_task> gc: workers){
				gc.join();
				gc = null;
			}
			workers = null;
			r = null;
			orb = null;
			numOfTasks = -1;
			numofCores = -1;				
		
		}catch (Exception e){
			System.err.println("problem in JCL facade destroy");
		}
	}
	
	
	@Override
	public boolean unRegister(String nickName) {
		// TODO Auto-generated method stub
		try{
			updateTask();		
		
			return orb.unRegister(nickName);
		}catch(Exception e){
			System.err.println("problem in JCL facade unRegister(String nickName)");
			return false;
		}
	}


	@Override
	public Object instantiateGlobalVar(String nickName, String varName, File[] jars,
			Object[] defaultVarValue) {
		try{
			updateTask();
			
			return orb.instantiateGlobalVar(nickName, varName, jars, defaultVarValue);
		}catch(Exception e){
			System.err.println("problem in JCL facade instantiateGlobalVar(String varName, File[] jars, Object[] defaultVarValue)");
			return null;
		}
	}


	@Override
	public boolean instantiateGlobalVar(String varName, Object instance) {
		// TODO Auto-generated method stub
		try{
			updateTask();		
		
			return orb.instantiateGlobalVar(varName, instance);
		}catch(Exception e){
			System.err.println("problem in JCL facade instantiateGlobalVar(String varName, Object instance)");
			return false;
		}
	}	
	
	@Override
	public boolean containsTask(String nickName) {
		try{
					
			return orb.containsTask(nickName);
			
		}catch(Exception e){
			System.err.println("problem in JCL facade containsTask(String nickName)");
			return false;
		}
	}


	@Override
	public boolean containsGlobalVar(String nickName) {
		try{
			
			return orb.containsGlobalVar(nickName);
			
		}catch(Exception e){
			System.err.println("problem in JCL facade containsGlobalVar(String nickName)");
			return false;
		}
	}
	
	protected static synchronized void updateTask(){
		numOfTasks++;
	}
	
	public static JCL_facade getInstance(){
		return Holder.instance;
	}
	
	private static class Holder{
		private static final JCL_facade instance = new JCL_FacadeImpl();
	}
		
}
